package com.incognito.spur.map.parser;

import java.util.HashMap;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.incognito.spur.map.Layer;
import com.incognito.spur.map.LayerType;
import com.incognito.spur.map.TiledMap;
import com.incognito.spur.map.TiledMapException;
import com.incognito.spur.map.utility.Point;

public class MapParserHandler extends DefaultHandler {

	private static final String MAP_ELEMENT = "map";
	private static final String TILE_ELEMENT = "tile";
	private static final String TILESET_ELEMENT = "tileset";
	private static final String LAYER_ELEMENT = "layer";
	
	private TiledMap map;
	
	private HashMap<Integer, Point> tiles;

	private Layer tempLayer;
	
	private StringBuffer textBuffer;
	
	/**
	 * Constructor to set the map object.
	 * 
	 * @param map
	 * 		The map object to set.
	 */
	public MapParserHandler(TiledMap map){
		this.map = map;
	}
	
	/**
	 * Handles parsing the actual text of an element. Stores the text in a buffer
	 * so it can be read when needed.
	 */
	@Override
	public void characters(char[] ch, int start, int length) throws SAXException{
		super.characters(ch, start, length);
		textBuffer.append(new String(ch, start, length));
	}
	
	/**
	 * Does some initialization before we start processing the XML document.
	 */
	@Override
	public void startDocument() throws SAXException {
		super.startDocument();
		textBuffer = new StringBuffer();
		tiles = new HashMap<Integer, Point>();
	}

	/**
	 * Handles any processing that needs to be done a the start of an element
	 */
	@Override
	public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
		super.startElement(uri, localName, name, attributes);
		
		// Process <map> element
		if (name.equals(MAP_ELEMENT)){
			processMapAttributes(attributes);
		
		// Process <tileset> element
		}else if (name.equals(TILESET_ELEMENT)){
			processTilesetAttributes(attributes);
			
		// Process <tile> element
		}else if (name.equals(TILE_ELEMENT)){
			processTileAttributes(attributes);
			
		// Process <layer> element
		}else if (name.equals(LAYER_ELEMENT)){
			tempLayer = new Layer();
			processLayerAttributes(attributes);
		}
	}

	/**
	 * Handles the end elements for XML. This does most of the actual processing
	 * work.
	 */
	@Override
	public void endElement(String uri, String localName, String name) throws SAXException {
		super.endElement(uri, localName, name);
		
		// Set completed layer
		if (name.equals(LAYER_ELEMENT)){
			finishLayerProcessing();
		}else if (name.equals(TILESET_ELEMENT)){
			map.getRenderer().setTiles(tiles);
		}
		
		textBuffer = new StringBuffer();
	}
	
	/**
	 * Processes <tileset> attributes.
	 * 
	 * @param attributes
	 * 		The attributes associated with this tileset.
	 */
	protected void processTilesetAttributes(Attributes attributes) throws SAXException{
		try{
			map.getRenderer().loadTileset(attributes.getValue("filepath"));
			map.getRenderer().setTileHeight(Integer.parseInt(attributes.getValue("tileHeight")));
			map.getRenderer().setTileWidth(Integer.parseInt(attributes.getValue("tileWidth")));
		}catch(TiledMapException e){
			throw new SAXException(e);
		}
	}
	
	/**
	 * Processes <map> attributes.
	 * 
	 * @param attributes
	 * 		The attributes associated with this map.
	 */
	protected void processMapAttributes(Attributes attributes){
		
		map.setHeight(Integer.parseInt(attributes.getValue("height")));
		map.setWidth(Integer.parseInt(attributes.getValue("width")));
		map.setName(attributes.getValue("name"));
	}
	
	/**
	 * Processes <tile> attributes to build up the hash map of tiles.
	 * 
	 * @param attributes
	 * 		The attributes associated with this tileset.
	 */
	protected void processTileAttributes(Attributes attributes){
	
		int id = Integer.parseInt(attributes.getValue("id"));
		int x = Integer.parseInt(attributes.getValue("x"));
		int y = Integer.parseInt(attributes.getValue("y"));
		
		Point p = new Point(x, y);
		
		tiles.put(id, p);
	}
	
	/**
	 * Processes <layer> attributes.
	 * 
	 * @param attributes
	 * 		The attributes associated with this layer.
	 */
	protected void processLayerAttributes(Attributes attributes){
		
		LayerType layerType = LayerType.valueOf(attributes.getValue("type"));
		String name = attributes.getValue("name");
		boolean visible = Boolean.valueOf(attributes.getValue("visible"));
		
		// Create layer and set attributes
		tempLayer.setName(name);
		tempLayer.setType(layerType);
		tempLayer.setVisible(visible);
	}
	
	/**
	 * Called after reaching a </layer> tag. This parses out the layer grid
	 * and adds the layer to the map.
	 */
	protected void finishLayerProcessing(){
		
		// Parse the grid
		if (textBuffer != null){
			
			int[][] grid = new int[map.getHeight()][map.getWidth()];
			
			String[] rows = textBuffer.toString().trim().split("\n");
			for (int i = 0; i < rows.length; i++){
				String[] cols = rows[i].trim().split(",");
				for (int j = 0; j < cols.length; j++){
					grid[i][j] = Integer.parseInt(cols[j]);
				}
			}
			
			tempLayer.setGrid(grid);
		}
		
		// collision layer
		if (LayerType.collision == tempLayer.getType()){
			map.setCollisionLayer(tempLayer);
						
		// normal layer
		}else if (LayerType.normal == tempLayer.getType()){
			map.addNormalLayer(tempLayer);
						
		// overlay layer
		}else if (LayerType.overlay == tempLayer.getType()){
			map.addOverlayLayer(tempLayer);
		}
		
		tempLayer = null;
	}
	
	/**
	 * Simple getter for the map itself.
	 * 
	 * @return
	 * 		The map in its current state.
	 */
	public TiledMap getMap() {
		return map;
	}
	
	/**
	 * Simple setter for the map itself.
	 * 
	 * @param map
	 * 		The map to set.
	 */
	public void setMap(TiledMap map){
		this.map = map;
	}
	
}